
// ===============================================================================================================
// -*- C++ -*-
//
// ImageLoaders.cpp - Image loading routines.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <GfxLib.hpp>
#include <jpeglib.h>
#include <setjmp.h>

// JPEG library compatibility stuff:

// Custom error manager for the jpeg library.
struct jpegErrorMgr {

	jpeg_error_mgr pub;
	jmp_buf jumpBuffer;
};

// This routine will replace the standard error_exit() method.
METHODDEF(void) jpegErrorExit(j_common_ptr cinfo)
{
	cinfo->err->output_message(cinfo);

	jpegErrorMgr * errMgr = reinterpret_cast<jpegErrorMgr *>(cinfo->err);

	// Return control to the setjmp point:

	longjmp(errMgr->jumpBuffer, 1);
}

// Output error messages via the debug output.
METHODDEF(void) jpegOutputMessage(j_common_ptr cinfo)
{
	// Display the error message:

	char charBuf[JMSG_LENGTH_MAX + 1] = {0};

	cinfo->err->format_message(cinfo, charBuf);

	CommLib::DbgPrintf(PRINT_ERROR, "Fatal error in the JPEG library! %s", charBuf);
} 

// ===============================================================================================================

namespace GfxLib {

unsigned char * LoadBitmapImage(const char * fileName, int & width, int & height, int & bytesPerPix)
{
	assert(fileName != 0);

	// windows.h stuff
	BITMAPFILEHEADER bitmapFileHeader;
	BITMAPINFOHEADER bitmapInfoHeader;

	unsigned char * pixels = 0;
	FILE * fp = fopen(fileName, "rb");

	if (fp != 0)
	{
		fread(&bitmapFileHeader, sizeof(BITMAPFILEHEADER), 1, fp);

		if (memcmp(&bitmapFileHeader.bfType, "BM", 2) == 0)
		{
			fread(&bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, fp);

			fseek(fp, bitmapFileHeader.bfOffBits, SEEK_SET);

			// 32 & 24 bits bitmap images
			pixels = reinterpret_cast<unsigned char *>(malloc(bitmapInfoHeader.biWidth * bitmapInfoHeader.biHeight * (bitmapInfoHeader.biBitCount / 8)));

			if (pixels != 0)
			{
				unsigned long bmpStride = (bitmapInfoHeader.biWidth * (bitmapInfoHeader.biBitCount / 8));

				while ((bmpStride % 4) != 0)
				{
					++bmpStride;
				}

				const unsigned long bytesPerLine = (bitmapInfoHeader.biWidth * (bitmapInfoHeader.biBitCount / 8));
				const unsigned long padding = (bmpStride - bytesPerLine);

				for (long scanlines = 0; scanlines < bitmapInfoHeader.biHeight; ++scanlines)
				{
					unsigned char * LinePtr = (pixels + bmpStride * scanlines);
					fread(LinePtr, bytesPerLine, 1, fp);
					fseek(fp, padding, SEEK_CUR);
				}

				width = bitmapInfoHeader.biWidth;
				height = bitmapInfoHeader.biHeight;
				bytesPerPix = (bitmapInfoHeader.biBitCount / 8);
			}
		}

		fclose(fp);
	}
	else
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Failed to open file: \"%s\"", fileName);
	}

	CommLib::DbgPrintf(PRINT_MSG, "Loaded BMP image: \"%s\"", fileName);

	return (pixels);
}

unsigned char * LoadJpegImage(const char * fileName, int & width, int & height, int & bytesPerPix)
{
	assert(fileName != 0);

	FILE * file = 0;
	unsigned char * pixels = 0; // Dest pixels.
	JSAMPARRAY rowPtr = 0; // Output row buffer.

	jpeg_decompress_struct cinfo; // JPEG decompress.
	jpegErrorMgr errMgr; // We use our private extension of the JPEG error handler.

	// We set up the normal JPEG error routines, then override error_exit() and output_message().
	cinfo.err = jpeg_std_error(&errMgr.pub);
	cinfo.err->error_exit = jpegErrorExit;
	cinfo.err->output_message = jpegOutputMessage;

	// Establish the setjmp return context for jpegErrorExit() to use:
	if (setjmp(errMgr.jumpBuffer))
	{
		// If we get here, the JPEG code has signaled an error.
		// We need to clean up the JPEG objects and return an error.

		if (rowPtr)
		{
			free(rowPtr);
		}

		if (pixels)
		{
			free(pixels);
		}

		if (file)
		{
			fclose(file);
		}

		jpeg_destroy_decompress(&cinfo);
		return (0);
	}

	// Open file:
	file = fopen(fileName, "rb");

	if (!file)
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Failed to open file: \"%s\"", fileName);
		return (0);
	}

	// Step 1: Allocate and initialize JPEG decompression object:
	jpeg_create_decompress(&cinfo);

	// Step 2: Specify data source:
	jpeg_stdio_src(&cinfo, file);

	// Step 3: Read file parameters with jpeg_read_header():
	jpeg_read_header(&cinfo, TRUE);

	// Step 4: Start decompressor:
	jpeg_start_decompress(&cinfo);

	// Allocate memory for the pixel buffer:
	pixels = reinterpret_cast<unsigned char *>(malloc(cinfo.output_width * cinfo.output_height * cinfo.output_components));

	if (!pixels)
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Memory allocation failure when trying to decompress a JPEG image");
		longjmp(errMgr.jumpBuffer, 1);
	}

	// Create an array of row pointers:
	rowPtr = reinterpret_cast<JSAMPARRAY>(malloc(cinfo.output_height * sizeof(JSAMPARRAY)));

	if (!rowPtr)
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Memory allocation failure when trying to decompress a JPEG image");
		longjmp(errMgr.jumpBuffer, 1);
	}

	const JDIMENSION rowStride = (cinfo.output_width * cinfo.output_components);

	for (JDIMENSION i = 0; i < cinfo.output_height; ++i)
	{
		rowPtr[i] = &(pixels[i * rowStride]);
	}

	JDIMENSION rowsRead = 0;

	while (cinfo.output_scanline < cinfo.output_height) 
	{
		rowsRead += jpeg_read_scanlines(&cinfo, &rowPtr[rowsRead], (cinfo.output_height - rowsRead));
	}

	// Delete the temporary row pointers:
	free(rowPtr);

	// Step 5: Finish decompression:
	jpeg_finish_decompress(&cinfo);

	// Step 6: Release JPEG decompression object:
	jpeg_destroy_decompress(&cinfo);

	if (errMgr.pub.num_warnings != 0)
	{
		CommLib::DbgPrintf(PRINT_WARN, "JPEG library reported warnings while decompressing an image...");
	}

	// All good, set the output data and we are done.
	width = cinfo.output_width;
	height = cinfo.output_height;
	bytesPerPix = cinfo.output_components;

	CommLib::DbgPrintf(PRINT_MSG, "Loaded JPEG image: \"%s\"", fileName);

	return (pixels);
}

unsigned char * LoadTgaImage(const char * fileName, int & width, int & height, int & bytesPerPix)
{
	/**
	 * An uncompressed TGA image loader.
	 * Author: Tom Arnold
	 * Available at: http://www.dreamincode.net/code/snippet2076.htm
	 */

	FILE * fp = NULL;
	int t;
	short int depth = 0;
	short int w = 0, h = 0;
	unsigned char * data = 0;

	// Open the file in binary mode.
	fp = fopen(fileName, "rb");

	// Problem opening file?
	if (fp == NULL)
	{
		CommLib::DbgPrintf(PRINT_ERROR, "Problem opening TGA file: \"%s\"", fileName);
	}
	else
	{
		// Load information about the tga, aka the header:

		// Seek to the width.
		fseek(fp, 12, SEEK_SET);
		fread(&w, sizeof(short int), 1, fp);

		// Seek to the height.
		fseek(fp, 14, SEEK_SET);
		fread(&h, sizeof(short int), 1, fp);

		// Seek to the depth.
		fseek(fp, 16, SEEK_SET);
		fread(&depth, sizeof(unsigned char), 1, fp);

		// Load the actual image data:

		// Total bytes = h * w * components per pixel.
		t = h * w * (depth / 8);

		// Allocate memory for the image data.
		data = (unsigned char *)malloc(sizeof(unsigned char) * t);

		// Seek to the image data.
		fseek(fp, 18, SEEK_SET);
		fread(data, sizeof(unsigned char), t, fp);

		// We're done reading.
		fclose(fp);

		CommLib::DbgPrintf(PRINT_MSG, "Loaded TGA image: \"%s\"", fileName);
	}

	width = w;
	height = h;
	bytesPerPix = depth / 8;

	return (data);
}

}; // namespace GfxLib {}